package com.pxene.dmp.common;

import com.fasterxml.jackson.annotation.JsonIgnore;
import net.sf.ehcache.Cache;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public final class SAuthRss
{
    public static enum SEED_TYPE
    {
        CURRENTDAY,  ROT13MD5REF;

        private SEED_TYPE() {}
    }

    private static final Logger LOGGER = LogManager.getLogger(SAuthRss.class);
    private final ConcurrentHashMap<String, AuthEntity> authStore = new ConcurrentHashMap();
    private final Boolean enableCache;
    private final Cache authSignCache;
    private static final AtomicLong saltNumber = new AtomicLong(0L);

    public static final class AuthEntity
    {
        public String ref;
        public UEncrypt.HASH_TYPE hashType;
        public String hashedPwd;
        public SAuthRss.SEED_TYPE seedType;

        public AuthEntity() {}

        public AuthEntity(String _ref, UEncrypt.HASH_TYPE _hashType, String _hashedPwd, SAuthRss.SEED_TYPE _seedType)
        {
            this.ref = _ref;
            this.hashType = _hashType;
            this.hashedPwd = _hashedPwd;
            this.seedType = _seedType;
        }

        @JsonIgnore
        public final String getIdentifier()
                throws Exception
        {
            return SAuthRss.getIdentifier(this.ref);
        }

        @JsonIgnore
        public final String getDesc()
                throws Exception
        {
            return UFormat.buf2str(new String[] { this.ref, "|", this.hashType.name(), "|", this.hashedPwd, "|", this.seedType.name(), "|", getSign() });
        }

        @JsonIgnore
        public final String getSign()
                throws Exception
        {
            return SAuthRss.getSign(this.ref, this.hashType, this.hashedPwd, this.seedType);
        }
    }

    public static final String getIdentifier(String _ref)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "getIdentifier", new Object[] { _ref });


        return _ref;
    }

    public static final String getSeed(String _ref, SEED_TYPE _seedType)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "getSeed", new Object[] { _ref, _seedType });
        if (_seedType.equals(SEED_TYPE.CURRENTDAY)) {
            return UFormat.date2str(new Date(), UFormat.DF_DATE.FORMAT21.getFormat());
        }
        if (_seedType.equals(SEED_TYPE.ROT13MD5REF)) {
            return UEncrypt.getHash(UEncrypt.HASH_TYPE.MD5, UEncrypt.rot13(_ref));
        }
        throw new Exception("SAuthRss : No such seed type, failed to getSeed");
    }

    public static final String getSign(String _ref, UEncrypt.HASH_TYPE _hashType, String _hashedPwd, SEED_TYPE _seedType)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "getSign", new Object[] { _ref, _hashType, _hashedPwd, _seedType });

        return UEncrypt.getHash(_hashType, UFormat.buf2str(new String[] { "ref=", _ref, "&password=", _hashedPwd, "&seed=", getSeed(_ref, _seedType) }));
    }

    public SAuthRss()
            throws Exception
    {
        this(Boolean.valueOf(false));
    }

    public SAuthRss(Boolean _enableCache)
            throws Exception
    {
        this.enableCache = _enableCache;
        if (this.enableCache.booleanValue()) {
            this.authSignCache = FEhcache.getMemCache(UFormat.buf2str(new String[]{"AuthRssSignCache-", String.valueOf(saltNumber.getAndIncrement())}));
        } else {
            this.authSignCache = null;
        }
    }

    public final Boolean existsIdentifier(String _ref)
            throws Exception
    {
        return Boolean.valueOf(this.authStore.containsKey(getIdentifier(_ref)));
    }

    public final AuthEntity addAuth(String _ref, UEncrypt.HASH_TYPE _hashType, String _pwd, SEED_TYPE _seedType)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "addAuth", new Object[] { _ref, _hashType, _pwd, _seedType });
        if (existsIdentifier(_ref).booleanValue()) {
            throw new Exception("SAuthRss : Auth already existed, failed to addAuth");
        }
        AuthEntity targetEntity = new AuthEntity(_ref, _hashType, UEncrypt.getHash(_hashType, _pwd), _seedType);
        this.authStore.put(getIdentifier(_ref), targetEntity);

        LOGGER.debug(UFormat.buf2str(new String[] { "SAuthRss : succeed to addAuth [ ", targetEntity.getDesc(), " ]" }));
        return targetEntity;
    }

    public final void deleteAuth(String _ref)
            throws Exception
    {
        if (!existsIdentifier(_ref).booleanValue()) {
            throw new Exception("SAuthRss : Auth not existed, failed to deleteAuth");
        }
        AuthEntity targetEntity = (AuthEntity)this.authStore.get(getIdentifier(_ref));
        this.authStore.remove(targetEntity.getIdentifier());
        LOGGER.debug(UFormat.buf2str(new String[] { "SAuthRss : succeed to deleteAuth [ ", targetEntity.getDesc(), " ]" }));
    }

    public final AuthEntity updateAuth(String _ref, String _sign, UEncrypt.HASH_TYPE _hashType, String _pwd, SEED_TYPE _seedType)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "updateAuth", new Object[] { _ref, _sign, _hashType, _pwd, _seedType });

        AuthEntity targetEntity = verifyAuth(_ref, _sign);

        targetEntity.hashType = _hashType;
        targetEntity.hashedPwd = UEncrypt.getHash(_hashType, _pwd);
        targetEntity.seedType = _seedType;
        this.authStore.put(targetEntity.getIdentifier(), targetEntity);

        LOGGER.debug(UFormat.buf2str(new String[] { "SAuthRss : succeed to updateAuth [ ", targetEntity.getDesc(), " ]" }));
        return targetEntity;
    }

    public final AuthEntity verifyAuth(String _ref, String _sign)
            throws Exception
    {
        UFormat.checkEmptyParam("SAuthRss", "verifyAuth", new Object[] { _ref, _sign });
        if (!existsIdentifier(_ref).booleanValue()) {
            throw new Exception("SAuthRss : No such auth, failed to loginAuth: " + _ref);
        }
        AuthEntity targetEntity = (AuthEntity)this.authStore.get(getIdentifier(_ref));
        if (this.enableCache.booleanValue())
        {
            String targetSign = (String) FEhcache.getCacheContent(this.authSignCache, getIdentifier(_ref));
            if ((targetSign != null) && (targetSign.equals(_sign)))
            {
                LOGGER.debug("RxServer : Auth cache hit, skip auth: " + _ref);
                return targetEntity;
            }
            LOGGER.debug("RxServer : Auth cache miss, execute auth: " + _ref);
        }
        String targetSign = targetEntity.getSign();
        if (_sign.equals(targetSign))
        {
            if (this.enableCache.booleanValue()) {
                FEhcache.setCacheContent(this.authSignCache, getIdentifier(_ref), targetSign);
            }
            return targetEntity;
        }
        if (targetEntity.seedType == SEED_TYPE.CURRENTDAY)
        {
            String newSign = getCompatibleSign(-1L, targetEntity);
            if (newSign == null) {
                throw new Exception("SAuthRss : Sign is wrong, failed to loginAuth: " + _ref);
            }
            if (!newSign.equals(_sign)) {
                throw new Exception("SAuthRss : Compatible Sign is wrong alse, failed to loginAuth: " + _ref);
            }
            LOGGER.debug("RxServer : Auth cache and new sign miss , execute compatible sign auth: " + _ref);
            return targetEntity;
        }
        throw new Exception("SAuthRss : Sign is wrong, failed to loginAuth: " + _ref);
    }

    public final String exportStore()
            throws Exception
    {
        String targetJson = UFormat.obj2json(this.authStore);
        LOGGER.debug(UFormat.buf2str(new String[] { "SAuthRss : export auth store [ ", targetJson, " ]" }));
        return targetJson;
    }

    public final void importStore(String _storeJson)
            throws Exception
    {
        String currJson = exportStore();
        if (currJson.equals(_storeJson))
        {
            LOGGER.debug("SAuthRss : import auth store skipped, same content");
        }
        else
        {
            this.authStore.clear();
            this.authStore.putAll(UFormat.json2map(_storeJson, ConcurrentHashMap.class, String.class, AuthEntity.class));
            LOGGER.debug(UFormat.buf2str(new String[] { "SAuthRss : import auth store [ ", _storeJson, " ]" }));
        }
    }

    protected static long timeWindow = 60000L;
    protected static TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");

    public static final long getCompatibleTime(long now)
    {
        Calendar time = Calendar.getInstance(tz);
        if (-1L != now) {
            time.setTimeInMillis(now);
        }
        Calendar t0 = Calendar.getInstance(tz);
        t0.setTimeInMillis(time.getTimeInMillis());
        t0.set(11, 0);
        t0.set(12, 0);
        t0.set(13, 0);
        t0.set(14, 0);
        if (timeWindow > time.getTimeInMillis() - t0.getTimeInMillis())
        {
            t0.set(6, t0.get(6) - 1);
            return t0.getTimeInMillis();
        }
        t0.set(6, t0.get(6) + 1);
        if (timeWindow > t0.getTimeInMillis() - time.getTimeInMillis()) {
            return t0.getTimeInMillis();
        }
        return 0L;
    }

    public static final String getCompatibleSign(long t, AuthEntity entity)
            throws Exception
    {
        long ct = getCompatibleTime(t);
        if (0L == ct) {
            return null;
        }
        String seed = UFormat.date2str(new Date(ct), UFormat.DF_DATE.FORMAT21.getFormat());
        String signV = UFormat.buf2str(new String[] { "ref=", entity.ref, "&password=", entity.hashedPwd, "&seed=", seed });
        return UEncrypt.getHash(entity.hashType, signV);
    }
}
